import pdb
from django.conf import settings
# from django.utils.log import logging
import logging
import re
from rest_framework_simplejwt.settings import api_settings
from rest_framework_simplejwt.views import TokenRefreshView, TokenObtainPairView
from rest_framework.exceptions import NotFound, MethodNotAllowed, ValidationError
from django.core.cache import cache
from django.db import transaction
from django_filters.rest_framework import DjangoFilterBackend
from uuid import uuid1
from django.contrib.auth.hashers import make_password
from django.db.utils import IntegrityError
from rest_framework_simplejwt.tokens import RefreshToken
from .serializers import UserSerializer, ArticleSerializer, CommentSerializer, NotificationSerializer, UserInfoSerializer
from .models import User, Article, Comment, Notification, UserInfo
from .paginations import ArticlePageNumberPagination, CommentPageNumberPagination, NotificationPageNumberPagination, UserPageNumberPagination
from .tasks import notify_all_followers, notify_the_replied
from rest_framework.viewsets import  ModelViewSet, GenericViewSet
from rest_framework.decorators import api_view, action, authentication_classes
from rest_framework.response import Response
from rest_framework import status, permissions, filters,  mixins
from rest_framework_simplejwt.exceptions import TokenError, InvalidToken
from .permissions import UserIsSelfOrReadOnly, IsAuthorOrReadOnly, CanDeleteCommentOrReadOnly, UserIsSelf
from .authentication import MyJWTAuthentication
from .throttles import RefreshBurstRateThrottle, RefreshSustainedRateThrottle
from django_redis import get_redis_connection
from django.core.mail import send_mail
import os
# Create your views here.

logger = logging.getLogger("my_logger")

class UserViewSet(ModelViewSet):
    queryset = User.objects.all()
    serializer_class = UserSerializer
    pagination_class = UserPageNumberPagination
    permission_classes = [UserIsSelf]

    def update(self, request, *args, **kwargs):
        return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)

    @action(methods=['post'], detail=True)
    def change_password(self, request, *args, **kwargs):
        user = request.user
        old_password = request.data.get('oldPassword')
        if old_password is None:
            return Response(data={'error': '需要旧的密码'}, status=status.HTTP_400_BAD_REQUEST)
        if not user.check_password(old_password):
            return Response(data={'error': '旧的密码错误'}, status=status.HTTP_400_BAD_REQUEST)

        password = request.data.get('password')
        if password is None:
            return Response(data={'error': '需要密码'}, status=status.HTTP_400_BAD_REQUEST)
        password_confirm = request.data.get('passwordConfirm')
        if password_confirm is None:
            return Response(data={'error': '需要确认密码'}, status=status.HTTP_400_BAD_REQUEST)
        if password != password_confirm:
            return Response(data={'error': '两次密码不一致'}, status=status.HTTP_400_BAD_REQUEST)
        if old_password != password:
            user.set_password(password)
            user.save()
        return Response(status=status.HTTP_200_OK)

    @action(methods=['post'], detail=True)
    def validate_email(self, request, *args, **kwargs):
        user = request.user
        to_email = self._get_email(request)
        # host = request.headers.get('Host')
        host = settings.DEPOLY_SERVER

        token = str(uuid1())
        cache.set(f"{user.id}:email_token", token, 600)
        # 异步发送报错dns解析错误
        # send_email.delay(subject='email validation',
        #                  message=f"Please visit this address {host}/emailValidationConfirm/?token={token} within 10 minutes to validate your email",
        #                  recipient=to_email)
        # #直接发送邮件没问题
        send_mail(subject='email validation', message=f"Please visit this address {host}/emailValidationConfirm/?token={token} within 10 minutes to validate your email",
        from_email=None, recipient_list=[to_email])
        if user.email != to_email:
            user.email = to_email
            user.email_validated = False
            user.save()
        return Response(status=status.HTTP_200_OK)

    @action(methods=['get'], detail=True)
    def validate_email_confirm(self, request, *args, **kwargs):
        user = request.user
        token = cache.get(f"{user.id}:email_token")
        if token is not None:
            request_token = request.query_params.get('token')
            if request_token and request_token == token:
                user.email_validated = True
                user.save()
                return Response(status=status.HTTP_200_OK)
        return Response(data={'error': 'token错误或者失效，请重新验证'}, status=status.HTTP_400_BAD_REQUEST)

    @action(methods=['post'], detail=False, permission_classes=[])
    def reset_password_email(self, request, *args, **kwargs):
        email = self._get_email(request)
        try:
            user = User.objects.get(email=email, email_validated=True)
        except User.DoesNotExist:
            return Response(data={'error': '没有该邮箱，或者该邮箱未验证过'}, status=status.HTTP_400_BAD_REQUEST)
        token = str(uuid1())
        cache.set(f"reset_password_token:{token}", 1, 600)
        # host = request.headers.get('Host')
        host = settings.DEPOLY_SERVER
        # 异步发送报错dns解析错误
        # send_email.delay(subject='reset password',
        #                  message=f"Please click this <a>{host}/resetPassword/?token={token}&email={email}</a> to reset your password",
        #                  recipient=email)
        # #直接发送邮件没问题
        send_mail(subject='reset password', message=f"Please visit this address {host}/resetPassword/?token={token}&email={email} to reset your password",
        from_email=None, recipient_list=[email])
        return Response(status=status.HTTP_200_OK)

    @action(methods=['post'], detail=False, permission_classes=[])
    def reset_password_confirm(self, request, *args, **kwargs):
        token = request.data.get('token')
        if token is None:
            return Response({'error': '需要token'}, status=status.HTTP_400_BAD_REQUEST)
        if cache.get(f"reset_password_token:{token}") is None:
            return Response({'error': 'token已失效，请重新获取邮件'}, status=status.HTTP_400_BAD_REQUEST)

        try:
            password = request.data['password']
        except KeyError:
            raise ValidationError(detail='password needed')
        try:
            password_confirm = request.data['password_confirm']
        except KeyError:
            raise ValidationError(detail='password_confirm needed')

        if password != password_confirm:
            raise ValidationError(
                detail='password does not match with each other')

        email = self._get_email(request)
        user = self._get_user_by_email(email)
        user.password = make_password(password)
        user.save()
        return Response(status=status.HTTP_200_OK)

    def _get_email(self, request):
        email = request.data.get('email')
        if email is None:
            raise ValidationError(detail='需要邮箱地址')
        # 检查邮箱格式
        regex = re.compile(
            r'^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$')
        if regex.match(email) is None:
            raise ValidationError(
                detail='邮箱地址格式错误，应该只包含英文数字“-”和“_”')
        return email

    def _get_user_by_email(self, email):
        try:
            user = User.objects.get(email=email, email_validated=True)
        except User.DoesNotExist:
            raise NotFound()
        return user

    def create(self, request, *args, **kwargs):
        raise MethodNotAllowed(method='create')


@api_view(['POST'])
# @throttle_classes([RegisterBurstRateThrottle, RegisterSustainedRateThrottle])
def register(request):
    username = request.data['username']
    password = request.data['password']
    password_confirm = request.data['password_confirm']
    if password != password_confirm:
        return Response({"error": "两次密码不一致"}, status=status.HTTP_400_BAD_REQUEST)
    try:
        with transaction.atomic():
            user = User.objects.create(
                username=username)
            user.set_password(password)
            user.save()
            UserInfo.objects.create(
                user=user, nickname=''.join(str(uuid1()).split('-')))
    except IntegrityError as e:
        err = e.args[0].split('=')[-1].strip('\n')
        return Response({"error": err}, status=status.HTTP_400_BAD_REQUEST)
    refresh = RefreshToken.for_user(user)
    access = str(refresh.access_token)
    tokens = {
        'refresh': str(refresh),
        'access': access
    }
    cache.set(access, '', timeout=api_settings.ACCESS_TOKEN_LIFETIME.total_seconds())

    cache.set(f"{access}:refresh", tokens['refresh'],
                  timeout=api_settings.REFRESH_TOKEN_LIFETIME.total_seconds())
    return Response({"tokens": tokens, "id": user.id, "info": user.info.id}, status=status.HTTP_201_CREATED)


class LoginView(TokenObtainPairView):
    # throttle_classes = [LoginBurstRateThrottle, LoginSustainedRateThrottle]

    def post(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)

        try:
            serializer.is_valid(raise_exception=True)
        except TokenError as e:
            raise InvalidToken(e.args[0])

        tokens = serializer.validated_data
        access_token = tokens['access']
        cache.set(access_token, '',
                  timeout=api_settings.ACCESS_TOKEN_LIFETIME.total_seconds())

        cache.set(f"{access_token}:refresh", tokens['refresh'],
                  timeout=api_settings.REFRESH_TOKEN_LIFETIME.total_seconds())
        logger.info("login ok")
        return Response({"tokens": tokens, "id": serializer.user.id, "info": serializer.user.info.id}, status=status.HTTP_200_OK)


class MyTokenRefreshView(TokenRefreshView):
    throttle_classes = [RefreshBurstRateThrottle, RefreshSustainedRateThrottle]

    def post(self, request, *args, **kwargs):
        resp = super().post(request, *args, **kwargs)
        cache.set(resp.data['access'], '',
                  timeout=api_settings.ACCESS_TOKEN_LIFETIME.total_seconds())
        return resp


@api_view(['post'])
@authentication_classes([MyJWTAuthentication])
def logout(request, *args, **kwargs):
    '''
    to log out, delete the access token and refresh token in redis
    '''
    if not request.user.is_anonymous:
        access = request.auth.token.decode()
        cache.delete(access)
        cache.delete('f{access}:refresh')
    return Response(status=status.HTTP_200_OK)


class UserInfoViewSet(ModelViewSet):
    queryset = UserInfo.objects.all()
    serializer_class = UserInfoSerializer
    pagination_class = UserPageNumberPagination
    permission_classes = [
        permissions.IsAuthenticatedOrReadOnly, UserIsSelfOrReadOnly]

    def update(self, request, *args, **kwargs):
        '''
        delete old avatar file after uploading a new one
        '''
        old_avatar = request.user.info.avatar
        resp = super().update(request, *args, **kwargs)
        if old_avatar:
            file = os.path.join(os.path.dirname(settings.MEDIA_ROOT), old_avatar.url.lstrip('/').lstrip('\\'))
            try:
                os.unlink(file)
            except Exception as e:
                logger.warning(f"failed to delete file [{file}], error: <{e}>")
        return resp

    @action(methods=['POST'], detail=True)
    def follow(self, request, *args, **kwargs):
        id = kwargs['pk']
        if id == request.user.info.id:
            return Response({'error': '无法关注用户自己'}, status=status.HTTP_400_BAD_REQUEST)
        try:
            userinfo = UserInfo.objects.get(id=id)
        except User.DoesNotExist:
            raise NotFound()
        follower = request.user.info
        verb = 'followed'
        if follower in userinfo.followers.all():
            userinfo.followers.remove(follower)
            verb = 'unfollowed'
        else:
            userinfo.followers.add(follower)
        # create_notification.delay(
        #     owner_id=userinfo.id, obj_id=follower.id, verb=verb, extra='you')
        return Response(status=status.HTTP_200_OK, data=self.get_serializer(userinfo).data)

    @action(methods=['POST'], detail=False)
    def batch_unfollow(self, request):
        userinfo = request.user.info
        infos = request.data['infos']
        userinfo.followees.remove(*infos)
        return Response(status=status.HTTP_200_OK)

    @action(methods=['POST'], detail=False)
    def remove_followers(self, request):
        userinfo = request.user.info
        infos = request.data['infos']
        userinfo.followers.remove(*infos)
        return Response(status=status.HTTP_200_OK)

    @action(methods=['GET'], detail=True)
    def followers(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())
        queryset = queryset.filter(followees=kwargs['pk'])

        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)

    @action(methods=['GET'], detail=True)
    def followees(self, request, *args, **kwargs):
        queryset = self.filter_queryset(self.get_queryset())
        queryset = queryset.filter(followers=kwargs['pk'])

        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)


class ArticleViewSet(ModelViewSet):
    queryset = Article.objects.all()
    serializer_class = ArticleSerializer
    permission_classes = [
        permissions.IsAuthenticatedOrReadOnly, IsAuthorOrReadOnly]
    filter_backends = [DjangoFilterBackend, filters.OrderingFilter]
    filterset_fields = ['author', 'liked_by']
    ordering_fields = ['last_modified_at']
    pagination_class = ArticlePageNumberPagination

    def create(self, request, *args, **kwargs):
        user_id = request.user.info.id
        request.data['author'] = user_id
        content = request.data['content']
        logger.info(f"article content: <{content}>")
        resp = super().create(request, *args, **kwargs)
        logger.info("article creation done, before notify followers")
        notify_all_followers(
            followee_id=user_id, new_article_id=resp.data['id'])
        logger.info("notify followers done")
        return resp

    @action(methods=['post'], detail=True)
    def like(self, request, *args, **kwargs):
        pk = kwargs['pk']
        try:
            article = Article.objects.get(id=pk)
        except Article.DoesNotExist:
            raise NotFound()
        # duplicated add won't cause an error, but has no effect
        article.liked_by.add(request.user.info)
        return Response(data=self.get_serializer(article).data, status=status.HTTP_200_OK)

    @action(methods=['post'], detail=True)
    def unlike(self, request, *args, **kwargs):
        pk = kwargs['pk']
        try:
            article = Article.objects.get(id=pk)
        except Article.DoesNotExist:
            raise NotFound()
        # duplicated add won't cause an error, but has no effect
        article.liked_by.remove(request.user.info)
        return Response(data=self.get_serializer(article).data, status=status.HTTP_200_OK)

    @action(methods=['post'], detail=True)
    def thumbup(self, request, *args, **kwargs):
        pk = kwargs['pk']
        user = request.user
        _thumbup(Article, pk, user)
        try:
            article = Article.objects.get(id=pk)
        except Article.DoesNotExist:
            raise NotFound()
        return Response(data=self.get_serializer(article).data, status=status.HTTP_200_OK)


class CommentViewSet(ModelViewSet):
    queryset = Comment.objects.all()
    serializer_class = CommentSerializer
    filter_backends = [DjangoFilterBackend, filters.OrderingFilter]
    filterset_fields = ['article']
    ordering_fields = ['created_at']
    permission_classes = [
        permissions.IsAuthenticatedOrReadOnly, CanDeleteCommentOrReadOnly]
    pagination_class = CommentPageNumberPagination

    def create(self, request, *args, **kwargs):
        author_id = request.user.info.id
        request.data['author'] = author_id
        # if it's a reply, to make sure the reply and its parent have the same article id, get article id from its parent
        parent_id = request.data.get('parent')
        parent_author_id = None
        if parent_id is not None:
            try:
                parent = Comment.objects.get(id=parent_id)
                parent_author_id = parent.author.id
            except Comment.DoesNotExist:
                return Response({'err': 'parent not found'}, status=status.HTTP_404_NOT_FOUND)
            request.data['article'] = parent.article.id

        resp = super().create(request, *args, **kwargs)
        logger.info("before create notification for article creation")
        notify_the_replied(obj_id=author_id, new_comment_id=resp.data['id'], article_id=request.data['article'],
                                 parent_id=parent_id, parent_author_id=parent_author_id)

        return resp

    def update(self, request, *args, **kwargs):
        raise MethodNotAllowed(method='update')

    # 因为filterset_fields无法过滤parent为空的数据，所以此处单独处理该字段的过滤
    def get_queryset(self):
        qs = super().get_queryset()
        parent = self.request.query_params.get('parent', None)
        if parent is None:
            return qs
        try:
            parent = int(parent)
        except ValueError:
            return qs
        if parent == -1:
            qs = qs.filter(parent__isnull=True)
        if parent > 0:
            qs = qs.filter(parent=parent)
        return qs

    def filter_queryset(self, queryset):
        qs = super().filter_queryset(queryset)
        # print(qs.query)
        return qs

    def destroy(self, request, *args, **kwargs):
        # mark the comment as deleted, can not update anything else of the comment
        request.data.clear()
        request.data['is_deleted'] = True
        request.data['content'] = '已删除'
        kwargs['partial'] = True
        super().update(request, *args, **kwargs)
        return Response({'msg': 'deleted'}, status=status.HTTP_204_NO_CONTENT)

    @action(methods=['post'], detail=True)
    def thumbup(self, request, *args, **kwargs):
        pk = kwargs['pk']
        user = request.user
        _thumbup(Comment, pk, user)
        try:
            comment = Comment.objects.get(id=pk)
        except Comment.DoesNotExist:
            raise NotFound()
        return Response(data=self.get_serializer(comment).data, status=status.HTTP_200_OK)

    @action(methods=['get'], detail=True)
    def full_chat(self, request, *args, **kwargs):
        id = kwargs['pk']
        try:
            comment = Comment.objects.get(id=id)
        except Comment.DoesNotExist:
            raise NotFound()
        queryset = comment.get_descendants()

        queryset = self.filter_queryset(queryset)

        page = self.paginate_queryset(queryset)
        if page is not None:
            serializer = self.get_serializer(page, many=True)
            return self.get_paginated_response(serializer.data)

        serializer = self.get_serializer(queryset, many=True)
        return Response(serializer.data)


class NotificationViewSet(mixins.ListModelMixin, mixins.UpdateModelMixin, GenericViewSet):
    queryset = Notification.objects.all()
    serializer_class = NotificationSerializer
    permission_classes = [permissions.IsAuthenticated]
    pagination_class = NotificationPageNumberPagination

    def get_queryset(self):
        qs = super().get_queryset()
        return qs.filter(owner=self.request.user.info)

    def update(self, request, *args, **kwargs):
        raise MethodNotAllowed(method='update')

    @action(methods=['post'], detail=True)
    def read(self, request, *args, **kwargs):
        request.data.clear()
        request.data['is_read'] = True
        kwargs['partial'] = True
        return super().update(request, *args, **kwargs)

    @action(methods=['post'], detail=False)
    def read_many(self, request, *args, **kwargs):
        self.get_queryset().filter(
            id__in=request.data['notifications']).update(is_read=True)
        # notifications = self.get_queryset().filter(id__in=request.data['notifications'])
        # for notification in notifications:
        #     notification.is_read = True
        # Notification.objects.bulk_update(notifications, ['is_read'])
        # from django.db import connection
        # print(connection.queries)
        return Response(status=status.HTTP_200_OK)


def _thumbup(model, pk, user):
    con = get_redis_connection('default')
    cache_key = f'{model.__name__.lower()}_thumbups:{pk}'
    new_thumbup = user.id

    if not con.exists(cache_key):
        con.sadd(cache_key, new_thumbup)
        return

    if not con.sismember(cache_key, new_thumbup):
        con.sadd(cache_key, new_thumbup)
    else:
        con.srem(cache_key, new_thumbup)
    # sync to pg by crontab, not here
    # try:
    #     with transaction.atomic():
    #         obj = model.objects.select_for_update().get(id=pk)
    #         thumbup_list = [
    #         ] if obj.thumbuped_by == '' or obj.thumbuped_by is None else obj.thumbuped_by.split(';')
    #         user_id = str(user.id)
    #         if user_id not in thumbup_list:
    #             thumbup_list.append(user_id)
    #         else:
    #             thumbup_list.remove(user_id)
    #         obj.thumbuped_by = ';'.join(thumbup_list)
    #         obj.save()
    # except model.DoesNotExist:
    #     raise NotFound()
    return
